package com.atguigu.tingshu.common.aspect;


import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.common.anno.GuiguCache;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.model.album.AlbumInfo;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.Expression;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.concurrent.TimeUnit;


/**
 * 优化思想：把变化的东西，然后缓存切面类的使用者进行指定
 * 如何让缓存切面类的使用者指定变化的内容？ 就是给注解扩展对应的属性，然后注解的使用者给属性赋值
 */
/*
* 优化思想:把变化的东西,,在缓存切面类中使用者进行指定
* 如何让缓存切面类的使用指定变化的内容? 就是给注解扩展对应的属性,然后注解的使用者给属性赋值
* 我们优化三个点  redis的查询标志 返回对象类型   锁的类型
* */
@Component
@Aspect
@Slf4j
public class GuiguCacheAspect {

    @Autowired
    private RedisTemplate<String , String> redisTemplate ;

    @Autowired
    private RedissonClient redissonClient ;

    @Around("@annotation(guiguCache)")
    public Object around(ProceedingJoinPoint point , GuiguCache guiguCache) {

        String cacheKeyExpression = guiguCache.cacheKey();            // album:info:#{#params[0]}
        //对cacheKey进行解析
        String cacheKey = parseExpression(cacheKeyExpression, point, String.class);

        /**
         * 从Redis中获取到的数据，到底需要将其转换成什么类型，取决于目标方法的返回值类型。
         */
        // 获取目标方法的返回值类型。先获取目标方法对象，然后再调用目标方法对象的方法获取目标方法的返回值。
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        Type genericReturnType = method.getGenericReturnType();   //获取返回的对象类型

        // 从Redis的缓存中查询数据
        String json = redisTemplate.opsForValue().get(cacheKey);
        if(!StringUtils.isEmpty(json)) {
            log.info("从Redis中查询到了专辑数据...");
            return JSON.parseObject(json , genericReturnType) ;
        }

        boolean enableLock = guiguCache.enableLock();  //是否需要分布式锁
        String lockNameExpression = guiguCache.lockName();  // 分布式锁的名称 album:lock:#{params[0]}
        if(enableLock && !StringUtils.isEmpty(lockNameExpression)) {

            String lockName = parseExpression(lockNameExpression, point, String.class); //album:lock:12

            // 获取分布式锁
            RLock rLock = redissonClient.getLock(lockName);
            boolean tryLock = rLock.tryLock();
            if(tryLock) {

                log.info("线程{}获取到了分布式锁..." , Thread.currentThread().getId());

                try {

                    // 查询数据库
                    Object result = point.proceed() ;     // 调用目标方法

                    // 把从数据库中查询到的数据存储到Redis中
                    redisTemplate.opsForValue().set(cacheKey , JSON.toJSONString(result) , RedisConstant.ALBUM_TIMEOUT , TimeUnit.SECONDS);

                    // 返回
                    return result ;

                }catch (Throwable e) {
                    throw new RuntimeException(e.getMessage()) ;
                }finally {
                    rLock.unlock();
                    log.info("线程{}释放了分布式锁..." , Thread.currentThread().getId());
                }

            }else {
                log.info("线程{}没有获取到分布式锁...进行自旋" , Thread.currentThread().getId());
                return around(point , guiguCache) ;   // 进行自旋
            }

        }else {

            try {
                // 查询数据库
                Object result = point.proceed() ;     // 调用目标方法

                // 把从数据库中查询到的数据存储到Redis中
                redisTemplate.opsForValue().set(cacheKey , JSON.toJSONString(result) , RedisConstant.ALBUM_TIMEOUT , TimeUnit.SECONDS);

                // 返回
                return result ;
            }catch (Throwable throwable) {
                throw new RuntimeException(throwable.getMessage()) ;
            }

        }

    }

    private static final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();

    // 表达式解析的解析方法
    private <T> T parseExpression(String expressionStr , ProceedingJoinPoint point , Class<T> clazz) {
        Expression expression = spelExpressionParser.parseExpression(expressionStr, ParserContext.TEMPLATE_EXPRESSION);
        StandardEvaluationContext standardEvaluationContext = new StandardEvaluationContext() ;
        standardEvaluationContext.setVariable("params" , point.getArgs());      // 将调用目标方法的实际参数预设给表达式解析器对象
        T value = expression.getValue(standardEvaluationContext, clazz);
        return value ;
    }
}
